/*
 * Copyright (c) 2018-2023 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "subcmd.history.h"
#include "subcmd.h"

static int histReqCode[] =
{
    0, 0, 0, 0, 0, 0, QUEUE_HIST, 0, 0, 0, 0,
    HOST_HIST, JS_HIST, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0
};

HASH_TABLE_T *ghashJobRecord;
LIST_T *glistJobRecord;

static bool_t sboolFoundJobInRecord;

extern int get_record_handle(time_t [], struct recordFileHandle *);
extern struct record *read_record_fromhandle(struct recordFileHandle *);
extern struct record *get_next_record_4job(struct recordFileHandle *, unsigned int, JOBID_T *);

static void trans_histrecord_2json(struct record *, struct histReq *, json *);

static int set_jobhist_request(int, char **, struct recordRequest *);
static void set_request_numfile(char* , struct recordRequest *);
static int set_request_times(char *, time_t []);
static void read_record_files(struct recordRequest *);
static void display_job_record(struct recordRequest *);

/**
 * @brief       qp admin hist handler, to display queue, host or js history operation.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 * @param[in]   index         #3: index of the control, QUEUE_HIST, HOST_HIST or JS_HIST
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      2             #3: argument has syntax error
 * @retval      3             #4: failed to open record file
 * @retval      4             #5: not enough memory to print json string
 *
 */
int
admin_operate_hist(int argc, char **argv, admin_control_t index)
{
    struct histReq req;
    memset(&req, 0, sizeof(struct histReq));
    req.request_code = histReqCode[index];
    req.log_time[0] = 0;
    req.log_time[1] = 0;

    while (TRUE) {
        int option_index = 0;
        static struct option long_options[] = {
            {"help", no_argument, 0, 'h'},
            {"file", required_argument, 0, 'f'},
            {"time", required_argument, 0, 't'},
            {NULL, 0, 0, 0}
        };

        int opt;
        opt = getopt_long(argc, argv, "hf:t:", long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
	case 'f':
            if (strlen(optarg) > PATH_MAX -1) {

                fprintf(stderr,  "Specified file name is longer than %d.\n", PATH_MAX-1);
                exit(2);
            } else {
                req.search_filename = copy_string(optarg);
            }

	    break;
        case 't':
            if (0 > set_request_times(optarg, req.log_time)) {
                admin_usage(index);
                exit(2);
            }

            break;
        case 'h':
            admin_usage(index);
            exit(0);
        case '?':
	default:
            admin_usage(index);
            exit(1);
	}
    }

    switch (req.request_code) {
    case QUEUE_HIST:
    case HOST_HIST:
	if (argc > optind) {
            int numNames=0, all=FALSE;
            char **nameList=NULL;

            numNames = get_argument_names(argc, argv, optind, &nameList, &all, "queueC");
	    if (!all && numNames != 0) {
                // Set specific queue or host names.
		req.names = nameList;
                req.num_name = numNames;
	    }
	}

	break;
    case JS_HIST:
        if (argc > optind) {
            fprintf(stderr,  "Cannot specify argument for qp js hist.\n");
            exit(1);
        }

	break;
    default:
	fprintf(stderr, "Unknown operation code.\n");
	exit(1);
    }

    char strRecordFile[PATH_MAX+1];
    if (NULL == req.search_filename) {
        // record file is not specified, read the default one.
        snprintf(strRecordFile, PATH_MAX, "%s/qp.records", baseParams[BASE_WORKDIR].string_value);
    } else {
        snprintf(strRecordFile, PATH_MAX, "%s", req.search_filename);
    }

    FILE *fileRecord;
    fileRecord = fopen(strRecordFile, "r");
    if (NULL == fileRecord) {
        fprintf(stderr, "fopen(%s) failed due to %m.\n", strRecordFile);
	exit(3);
    }

    json *jsonHistory = json_CreateObject();

    unsigned int lineNum = 0;
    while (TRUE) {

        struct record *log;
        log = read_file_line(fileRecord, &lineNum); // read one line of the record file.
        if (NULL != log) {

            trans_histrecord_2json(log, &req, jsonHistory);
            continue;
        }

        if (quickpool_errno == ERROR_EOF) {
            break;
        }
    }

    if (0 < json_GetArraySize(jsonHistory)) {
        char *strJson;
        strJson = json_Print(jsonHistory);
        if (NULL != strJson) {
            fprintf(stdout, "%s\n", strJson);
            FREEUP(strJson);
        } else {

            perror("malloc");
            json_Delete(jsonHistory);
            fclose(fileRecord);

            exit(4);
        }
    } else {
        // nothing in json means there's no requested record found.
        fprintf(stderr, "No matching system history record found.\n");
    }

    json_Delete(jsonHistory);
    fclose(fileRecord);

    exit(0);
} // end function admin_operate_hist

/**
 * @brief       qp job hist handler, to display specific job record.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      2             #3: no job/array job found in record file
 *
 */
int
cmd_job_records(int argc, char **argv)
{
    struct recordRequest request;
    memset(&request, 0, sizeof(struct recordRequest));

    // first, parse the job hist options.
    if (set_jobhist_request(argc, argv, &request) == -1) {
        exit(1);
    }

    ghashJobRecord = gf_hash_make(50);
    glistJobRecord = gf_list_make("job record list");

    // read record file and put job record in hash.
    read_record_files(&request);

    if (request.options & OPT_CHRONICLE) {

        if (!sboolFoundJobInRecord) {

            printf("No matching job/record found\n");
            gf_hash_free(ghashJobRecord, NULL);

            exit(2);
        } else {
            printf("\n");
        }

        gf_hash_free(ghashJobRecord, NULL);

        // job record was displayed in read_record_files, we are done now.
        exit(0);
    }

    display_job_record(&request);

    FREEUP(request.user_name);
    FREEUP(request.job_ids);

    if (!sboolFoundJobInRecord) {
        if (request.options & OPT_ARRAY_INFO) {
            printf("No matching job array found\n");
        } else {
            printf("No matching job found\n");
        }

        gf_hash_free(ghashJobRecord, NULL);
        exit(2);
    }

    gf_hash_free(ghashJobRecord, NULL);
    exit(0);
} // end function cmd_job_records

/* ------------------------------------------------
 *
 *  below are static functions used in this file
 *
 * ------------------------------------------------ */

static void
jobhist_usage(char *cmd)
{
    fprintf(stdout, "qp job %s usage: ", cmd);
    fprintf(stdout, "[-h, --help] [-a, --all] [-d, --done] [-e, --exit]\n");
    fprintf(stdout, "\t\t   [-p, --pend] [-s, --suspend] [-r, --start]\n");
    fprintf(stdout, "\t\t   [-f, --file logfile_name | -n, --number num_logfiles|min_logfile, max_logfile]\n");
    fprintf(stdout, "\t\t   [-C, --complete time0,time1] [-S, --submit time0,time1] [-D, --dispatch time0,time1]\n");
    fprintf(stdout, "\t\t   [-T, --interval time0,time1]\n");
    fprintf(stdout, "\t\t   [-q, --queue queue_name] [-m, --node host_name] [-J, --jobname job_name]\n");
    fprintf(stdout, "\t\t   [-u, --user user_name| all] [-P, --project project_name] [jobId | \"jobId[index]\" ...]\n");
    fprintf(stdout, "or\n");
    fprintf(stdout, "\t\t   [-h] -t [-f logfile_name] [-n, --number num_logfiles|min_logfile, max_logfile]\n");

    return;
}

/**
 * @brief       parse job hist option to recordRequest.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 * @param[out]  request       #3: job hist options
 *
 * @retval      0             #1: succeed
 * @retval      -1            #2: failed to get my name
 *
 * @note        if option argument has syntax error, this function would exit directly.
 */
static int
set_jobhist_request(int argc, char **argv, struct recordRequest *request)
{
    extern int idxerrno;

    time_t defaultTime[2];
    defaultTime[0] = 0;
    defaultTime[1] = time(NULL);

    request->options = 0;
    request->options |= OPT_DFTSTATUS;
    request->options |= OPT_ALLPROJ;
    request->num_job = 0;
    request->queue[0] = '\0';
    request->node[0] = '\0';
    request->job_name = NULL;
    request->num_max_file = 1;
    request->project_name = NULL;
    request->search_time[0] = -1;
    request->search_time[1] = -1;

    request->user_id = getuid();
    request->user_name = getmy_uname();
    if (NULL == request->user_name) {
        fprintf(stderr, STR_FUNC_FAIL, __func__, "getmy_uname");
        return -1;
    }

    int numJobs, *idxList;
    while (TRUE) {
        int option_index = 0;
        static struct option long_options[] = {
            {"help", no_argument, 0, 'h'},
            {"array", no_argument, 0, 'A'},
            {"all", no_argument, 0, 'a'},
            {"done", no_argument, 0, 'd'},
            {"exit", no_argument, 0, 'e'},
            {"pend", no_argument, 0, 'p'},
            {"suspend", no_argument, 0, 's'},
            {"start", no_argument, 0, 'r'},
            {"chronicle", no_argument, 0, 't'},
            {"file", required_argument, 0, 'f'},
            {"queue", required_argument, 0, 'q'},
            {"node", required_argument, 0, 'm'},
            {"user", required_argument, 0, 'u'},
            {"jobname", required_argument, 0, 'J'},
            {"project", required_argument, 0, 'P'},
            {"complete", required_argument, 0, 'C'},
            {"dispatch", required_argument, 0, 'D'},
            {"submit", required_argument, 0, 'S'},
            {"interval", required_argument, 0, 'T'},
            {"number", required_argument, 0, 'n'},
            {NULL, 0, 0, 0}
        };

        int opt;
        opt = getopt_long(argc, argv, "hAadepsrtf:q:m:u:J:P:C:D:S:T:n:",
                          long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
        case 'A': // array job
            request->options |= OPT_ARRAY_INFO;
            break;
        case 'a': // all jobs
            request->options |= OPT_ALL;
            request->options &= ~OPT_DFTSTATUS;
            request->options &= ~OPT_DONE;
            request->options &= ~OPT_EXIT;
            request->options &= ~OPT_PEND;
            request->options &= ~OPT_SUSP;
            request->options &= ~OPT_RUN;

            break;
        case 'd': // done job
            if ((request->options & OPT_ALL) != OPT_ALL) {
                request->options &= ~OPT_DFTSTATUS;
                request->options &= ~OPT_EXIT;
                request->options |= OPT_DONE;
            }

            break;
        case 'e': // exit job
            if ((request->options & OPT_ALL) != OPT_ALL) {
                request->options &= ~OPT_DFTSTATUS;
                request->options &= ~OPT_DONE;
                request->options |= OPT_EXIT;
            }

            break;
        case 'p': // pending job
            if ((request->options & OPT_ALL) != OPT_ALL) {
                request->options &= ~OPT_DFTSTATUS;
                request->options |= OPT_PEND;
            }

            break;
        case 's': // started job
            if ((request->options & OPT_ALL) != OPT_ALL) {
                request->options &= ~OPT_DFTSTATUS;
                request->options |= OPT_SUSP;
            }

            break;
        case 'r': // running job
            if ((request->options & OPT_ALL) != OPT_ALL) {
                request->options &= ~OPT_DFTSTATUS;
                request->options |= OPT_RUN;
            }

            break;
        case 't': // display record according the log time.
            request->options |= OPT_CHRONICLE;
            request->options &= ~OPT_DFTSTATUS;
            request->options &= ~OPT_ALLPROJ;

            break;
        case 'f': // specify the file to be read.
            if (request->options & OPT_ELOGFILE) {
                fprintf(stderr, "Do not specify -f/--file option for multiple times.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            request->options |= OPT_ELOGFILE;
            if (request->options & OPT_NUMLOGFILE) {
                fprintf(stderr, "%s: -f/--file option does not work with -n/--number.\n", argv[0]);
                jobhist_usage(argv[0]);
                exit(1);
            }

            if (0 >= strlen(optarg)) {
                fprintf(stderr, "Must specify an record file with -f/--file option.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            if (strlen(optarg) >= PATH_MAX) {
                fprintf(stderr, "Specified record file name <%s> is longer than <%d>.\n",
                        optarg, PATH_MAX);
                exit(2);
            }

            strcpy(request->record_file_name, optarg);
            break;
        case 'u': // filter user jobs.
            if (strcmp(optarg, ALL_STRING) == 0) {
                request->options |= OPT_ALLUSERS;
                request->options &= ~OPT_USER;
            } else if ((request->options & OPT_ALLUSERS) != OPT_ALLUSERS) {
                request->options |= OPT_USER;

                struct passwd *pw = get_user_byname(optarg);
                if (NULL != pw) {
                    request->user_id = pw->pw_uid;
                } else {
                    request->user_id = -1;
                }

                FREEUP(request->user_name);
                request->user_name = copy_string(optarg);
            }

            break;
        case 'm': // filter job that running on specified node.
            if (request->options & OPT_HOST) {
                fprintf(stderr, "Do not specify -m/--node option for multiple times.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            if (0 >= strlen(optarg)) {
                fprintf(stderr, "Must specify a node name with -m/--node option.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            if (strlen(optarg) >= MAXHOSTNAMELEN) {
                fprintf(stderr, "Specified node name <%s> is longer than <%d>.\n",
                        optarg, MAXHOSTNAMELEN);
                exit(2);
            }

            request->options |= OPT_HOST;
            strcpy(request->node, optarg);

            break;
        case 'q': // filter job that belongs to specified queue.
            if (request->options & OPT_QUEUE) {
                fprintf(stderr, "Do not specify -q/--queue option for multiple times.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            if (0 >= strlen(optarg)) {
                fprintf(stderr, "Must specify a queue name with -q/--queue option.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            if (strlen(optarg) >= MAXNAMELEN) {
                fprintf(stderr, "Specified project name <%s> is longer than <%d>.\n",
                        optarg, MAXNAMELEN);
                exit(2);
            }

            request->options |= OPT_QUEUE;
            strcpy(request->queue, optarg);
            break;
        case 'J': // filter job that names with job name.
            if (request->options & OPT_JOBNAME) {
                fprintf(stderr, "Do not specify -J/--jobname option for multiple times.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            if (0 >= strlen(optarg)) {
                fprintf(stderr, "Must specify a job name with -J/--jobname option.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            request->options |= OPT_JOBNAME;
            request->job_name = optarg;

            if ((numJobs = get_job_idxlist(request->job_name, &idxList))==0 && idxerrno!=0) {
                exit(2);
            }

            break;
        case 'P': // filter job that belongs to specified project.
            if (request->options & OPT_PROJ) {
                fprintf(stderr, "Do not specify -P/--project option for multiple times.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            if (0 >= strlen(optarg)) {
                fprintf(stderr, "Must specify a project name with -P/--project option.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            if (strlen(optarg) >= MAXNAMELEN) {
                fprintf(stderr, "Specified project name <%s> is longer than <%d>.\n",
                        optarg, MAXNAMELEN);
                exit(1);
            }

            request->options |= OPT_PROJ;
            request->options &= ~OPT_ALLPROJ;
            request->project_name = copy_string(optarg);

            break;
        case 'C': // filter job that has been done since specified time.
            if (request->options & OPT_COMPLETE) {
                fprintf(stderr, "Do not specify -C/--complete option for multiple times.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            request->options |= OPT_COMPLETE;
            if ((request->options & OPT_ALL) != OPT_ALL) {
                request->options &= ~OPT_DFTSTATUS;
            }

            if ((request->options & OPT_EXIT) != OPT_EXIT) {
                request->options |= OPT_DONE;
            }

            if (0 > set_request_times(optarg, request->end_time)) {
                jobhist_usage(argv[0]);
                exit(2);
            }

            break;
        case 'D': // filter job that has been dispatched since specified time.
            if (request->options & OPT_DISPATCH) {
                fprintf(stderr, "Do not specify -D/--dispatch option for multiple times.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            request->options |= OPT_DISPATCH;
            if (0 > set_request_times(optarg, request->start_time)) {
                jobhist_usage(argv[0]);
                exit(2);
            }

            break; 
        case 'S': // filter job that has been submitted since the specified time.
            if (request->options & OPT_SUBMIT) {
                fprintf(stderr, "Do not specify -S/--submit option for multiple times.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            request->options |= OPT_SUBMIT;
            if (0 > set_request_times(optarg, request->submit_time)) {
                jobhist_usage(argv[0]);
                exit(2);
            }

            break;
        case 'T': // filter job record that between the specified begin time and end time.
            if (request->options & OPT_TIME_INTERVAL) {
                fprintf(stderr, "Do not specify -T/--interval option for multiple times.\n");
                jobhist_usage(argv[0]);
                exit(1);
            }

            request->options |= OPT_TIME_INTERVAL;
 
            if (0 > set_request_times(optarg, request->search_time)) {
                jobhist_usage(argv[0]);
                exit(1);
            }

            break;
        case 'n': // filter job record from the specified number of record files.
            if (request->options & OPT_ELOGFILE) {
                fprintf(stderr, "%s: -n/--number option does not work with -f/--file.\n", argv[0]);
                jobhist_usage(argv[0]);
                exit(1);
            }

            request->options |= OPT_NUMLOGFILE;
            set_request_numfile(optarg, request);

            if (0 == request->num_max_file) {
                request->num_max_file = 100;
            }

            break;
        case 'h':
            jobhist_usage(argv[0]);
            exit(0);
        case '?':
        default:
            jobhist_usage(argv[0]);
            exit(1);
	}
    }

    request->num_job = get_jobids_fromargv(argc, argv, &request->job_ids);

    if (request->options & OPT_CHRONICLE) {
        if (0 < request->num_job) {
            fprintf(stderr, "Do not specify jobId with -t/--chronicle option.\n");
            jobhist_usage(argv[0]);
            exit(2);
        }

	if (request->options & ~OPT_CHRONICLE & ~OPT_TIME_INTERVAL & ~OPT_ELOGFILE) {
            fprintf(stderr, "Specify -t/--chronicle option only with -T/--interval and -f/--file options.\n");
            jobhist_usage(argv[0]);
            exit(2);
        }

        if (request->search_time[0] == -1) {

            if (0 == defaultTime[0]) {
                request->search_time[0] = defaultTime[1] - ONEWEEK;
            } else {
                request->search_time[0] = defaultTime[0];
            }

            request->search_time[1] = defaultTime[1];
        }

        return 0;
    }

    if (!(request->options & OPT_TIME_INTERVAL)) {
        if ((0 < request->num_job) || (request->options & OPT_COMPLETE)
            || (request->options & OPT_SUBMIT) || (request->options & OPT_NUMLOGFILE)
            || (request->options & OPT_DISPATCH)) {

            if (!(request->options & (OPT_NUMLOGFILE | OPT_TIME_INTERVAL |
                                      OPT_SUBMIT | OPT_DISPATCH | OPT_ELOGFILE))) {
                if (0 == defaultTime[0]) {
                    request->search_time[0] = defaultTime[1] - ONEWEEK;
                } else {
                    request->search_time[0] = defaultTime[0];
                }

                request->search_time[1] = defaultTime[1];
            } else {
                request->search_time[0] = defaultTime[0];
                request->search_time[1] = defaultTime[1];
            }
        }

        if (!(request->options & OPT_NUMLOGFILE) && 0 == defaultTime[0]) {

            request->search_time[0] = -1;
            request->search_time[1] = -1;
        }
    }

    if (0 < request->num_job) {
        request->options |= OPT_JOBID;
        request->options |= OPT_ALLUSERS;
        request->options |= OPT_ALL;
        request->options &= ~OPT_USER;
        request->options &= ~OPT_DONE;
        request->options &= ~OPT_EXIT;
        request->options &= ~OPT_PEND;
        request->options &= ~OPT_SUSP;
        request->options &= ~OPT_RUN;
        request->options &= ~OPT_QUEUE;
        request->options &= ~OPT_HOST;
        request->options &= ~OPT_COMPLETE;
        request->options &= ~OPT_SUBMIT;
        request->options &= ~OPT_DISPATCH;
        request->options &= ~OPT_DFTSTATUS;
    }

    return 0;
} // end function set_jobhist_request

static bool_t
is_chkpnt_job(struct recordContent *content, struct recordJob *record)
{
    struct recordContent *loopContnet;
    struct QuickPoolJobInfo *job = record->job;
    for (loopContnet=record->record_head; loopContnet!=content; loopContnet=loopContnet->next) {

        if (loopContnet->kind == RECORD_TYPE_JOB_MODIFY) {
            job = loopContnet->modify_params;
        }
    }

    if (NULL == job) {
        return FALSE;
    }

    if ((job->submit->options & SUBOPT_RERUNNABLE)
        && (NULL == job->submit->chkpnt_dir || '\0' == job->submit->chkpnt_dir[0])) {

        return FALSE;
    }

    return TRUE;
} // end function is_chkpnt_job

/**
 * @brief       transfer record to json format.
 *
 * @param[in]   log           #1: record log to be transferred
 * @param[in]   req           #2: request rule to do the transfer
 * @param[out]  jobhist       #3: json format of the job record
 * @param[out]  syshist       #4: json format of the system record
 *
 */
static void
trans_record_2json(struct record *log, struct recordRequest *request, json *jobhist)
{
    struct QuickPoolJobInfo *job;
    struct recordContent *content;
    unsigned int i;

    char timebuf[TIMESTRINGLEN];
    ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &(log->log_time), timebuf);

    struct recordJob *record;
    switch (log->type) {
    case RECORD_TYPE_JOB_NEW: // JOB_NEW
    {
        job = read_jobcreate(log);
        if (job == NULL) {
            break;
        }

        json *jsonJobSubmit = submitrequest_2json(job);
        json_AddItemToObject(jobhist, "JOB_NEW", jsonJobSubmit);

        gf_xdr_free(libxdr_jobinfo, job);
        FREEUP(job);

        break;
    }
    case RECORD_TYPE_JOB_MODIFY: // JOB_MODIFY
    {
        struct recordJob **jobRecords;
        int numJobs=0;

        jobRecords = read_jobmodify(log, &numJobs, request);
        if (NULL == jobRecords) {
            break;
        }

        int i;
        for (i=0; i<numJobs; i++) {

            struct QuickPoolJobInfo *modifyJob = jobRecords[i]->record_tail->modify_params;
            if (NULL == modifyJob) {
                 continue;
            }

            struct submitRequest *submit = modifyJob->submit;
            if (NULL == submit) {
                continue;
            }

            json *jsonJobModify = json_CreateObject();

            json_AddNumberToObject(jsonJobModify, "JOBID", ARRAY_JOBID(modifyJob->jobid));
            if (0 < ARRAY_INDEX(modifyJob->jobid)) {
                json_AddNumberToObject(jsonJobModify, "JOB_INDEX", ARRAY_INDEX(modifyJob->jobid));
            }

            json_AddStringToObject(jsonJobModify, "MODIFY_TIME", timebuf);

            json *jsonJobSubmit = submitrequest_2json(modifyJob);
            json_AddItemToObject(jsonJobModify, "MODIFIED_REQUEST", jsonJobSubmit);

            json *jsonJobRemoveOption = json_CreateArray();
            if (submit->modn_options & SUBOPT_JOB_NAME) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-J"));
            }
            if (submit->modn_options & SUBOPT_QUEUE) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-q"));
            }
            if (submit->modn_options & SUBOPT_HOST ) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-m"));
            }
            if (submit->modn_options & SUBOPT_IN_FILE) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-i"));
            }
            if (submit->modn_options & SUBOPT_OUT_FILE) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-o"));
            }
            if (submit->modn_options & SUBOPT_ERR_FILE) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-e"));
            }
            if (submit->modn_options & SUBOPT_EXCLUSIVE) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-x"));
            }
            if (submit->modn_options & SUBOPT_NOTIFY_END) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-N"));
            }
            if (submit->modn_options & SUBOPT_NOTIFY_BEGIN) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-B"));
            }
            if (submit->modn_options & SUBOPT_RERUNNABLE) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-r"));
            }
            if (submit->modn_options & SUBOPT_DEPEND_COND) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-w"));
            }
            if (submit->modn_options & SUBOPT_RES_REQ ) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-R"));
            }
            if (submit->modn_options & SUBOPT_OTHER_FILES) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-f"));
            }
            if (submit->modn_options & SUBOPT_PRE_EXEC) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-E"));
            }
            if (submit->modn_options & SUBOPT_POST_EXEC) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-A"));
            }
            if (submit->modn_options & SUBOPT_LOGIN_SHELL) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-L"));
            }
            if (submit->modn_options & SUBOPT_MAIL_USER) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-u"));
            }
            if (submit->modn_options & SUBOPT_PROJECT_NAME) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-P"));
            }
            int j;
            for (j = 0; j < RESOURCE_LIMIT_NUM; j++) {
                if (submit->job_ulimit[j] != DELETE_NUMBER) {
                    continue;
                }

                switch(j) {
                case RESOURCE_LIMIT_CPU:
                    json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-c"));
                    break;
                case RESOURCE_LIMIT_FSIZE:
                    json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-F"));
                    break;
                case RESOURCE_LIMIT_DATA:
                    json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-D"));
                    break;
                case RESOURCE_LIMIT_STACK:
                    json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-S"));
                    break;
                case RESOURCE_LIMIT_CORE:
                    json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-C"));
                    break;
                case RESOURCE_LIMIT_RSS:
                    json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-M"));
                    break;
                case RESOURCE_LIMIT_SWAP:
                    json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-v"));
                    break;
                case RESOURCE_LIMIT_PROCESS:
                    json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-p"));
                    break;
                case RESOURCE_LIMIT_RUN:
                    json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-W"));
                    break;
                }
            }
            if (submit->num_job_slot == DEL_NUMPRO) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-n"));
            }
            if (submit->begin_time == DELETE_NUMBER) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-b"));
            }
            if (submit->terminate_time == DELETE_NUMBER) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-t"));
            }
            if (submit->modn_options & SUBOPT_JOB_PRIORITY) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-j"));
            }
            if (submit->modn_options2 & SUBOPT2_REQUEUE_EXITCODE) {
                json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-Q"));
            }

            json_AddItemToObject(jsonJobModify, "REMOVED_REQUEST", jsonJobRemoveOption);

            json_AddItemToObject(jobhist, "JOB_MODIFY", jsonJobModify);
        }

        break;
    }
    case RECORD_TYPE_JOB_START: // JOB_START
    case RECORD_TYPE_PRE_EXEC_START: // PRE_EXEC_START
    {
        record = read_jobrun(log, request);
        if (NULL == record) {
            break;
        }

        job = record->job;

        json *jsonJobStart = json_CreateObject();
        json_AddNumberToObject(jsonJobStart, "JOBID", ARRAY_JOBID(job->jobid));
        if (0 < ARRAY_INDEX(job->jobid)) {
            json_AddNumberToObject(jsonJobStart, "JOB_INDEX", ARRAY_INDEX(job->jobid));
        }
        json_AddStringToObject(jsonJobStart, "START_TIME", timebuf);

        int numAllocSlot=0;
        json *jsonNodeArray = json_CreateArray();
        for (i=0; i<job->num_execute_node; i++) {
            char strExecNode[PATH_MAX+1];

            snprintf(strExecNode, PATH_MAX, "%d*%s", job->num_slot[i], job->execute_nodes[i]);
	    json_AddItemToArray(jsonNodeArray, json_CreateString(strExecNode));
            numAllocSlot += job->num_slot[i];
        }
        json_AddNumberToObject(jsonJobStart, "NUM_ALLOCATE_SLOT", numAllocSlot);
        json_AddNumberToObject(jsonJobStart, "NUM_EXECUTE_NODE", job->num_execute_node);
        json_AddItemToObject(jsonJobStart, "EXECUTE_NODE", jsonNodeArray);

        if (log->type == RECORD_TYPE_PRE_EXEC_START) {
            json_AddItemToObject(jobhist, "PRE_EXEC_START", jsonJobStart);
        } else {
            json_AddItemToObject(jobhist, "JOB_START", jsonJobStart);
        }

        free_jobrecord(record);
        break;
    }
    case RECORD_TYPE_JOB_EXECUTE: // JOB_EXECUTE
    {
        record = read_jobstarted(log, request);
        if (NULL == record) {
            break;
        }

        job = record->job;

        json *jsonJobExecute = json_CreateObject();
        json_AddNumberToObject(jsonJobExecute, "JOBID", ARRAY_JOBID(job->jobid));
        if (0 < ARRAY_INDEX(job->jobid)) {
            json_AddNumberToObject(jsonJobExecute, "JOB_INDEX", ARRAY_INDEX(job->jobid));
        }

        json_AddStringToObject(jsonJobExecute, "EXECUTE_TIME", timebuf);

        if (NULL != job->execute_homedir && '\0' != job->execute_homedir[0]) {
            s2j_json_set_basic_element(jsonJobExecute, job, string,
                                       "EXECUTE_HOMEDIR", execute_homedir);
        }

        if (NULL != job->execute_cwd && '\0' != job->execute_cwd[0]) {
            s2j_json_set_basic_element(jsonJobExecute, job, string, "EXECUTE_CWD", execute_cwd);
        }

        if (NULL != job->execute_user && '\0' != job->execute_user[0]) {
            s2j_json_set_basic_element(jsonJobExecute, job, string, "EXECUTE_USER", execute_user);
        }

        if (0 < record->job_pid) {
            s2j_json_set_basic_element(jsonJobExecute, record, int, "EXECUTE_PID", job_pid);
        }

        json_AddItemToObject(jobhist, "JOB_EXECUTE", jsonJobExecute);

        free_jobrecord(record);
        break;
    }
    case RECORD_TYPE_JOB_STATUS: // JOB_STATUS
    {
        record = read_jobstatus(log, request);
        if (NULL == record) {
            break;
        }

        content = record->record_tail;

        json *jsonJobStatus = json_CreateObject();
        json_AddNumberToObject(jsonJobStatus, "JOBID", ARRAY_JOBID(record->job->jobid));
        if (0 < ARRAY_INDEX(record->job->jobid)) {
            json_AddNumberToObject(jsonJobStatus, "JOB_INDEX", ARRAY_INDEX(record->job->jobid));
        }

        const char *strStatus;
        strStatus = get_status(record->job);
        json_AddStringToObject(jsonJobStatus, "STATUS", strStatus);
        strStatus = get_post_status(record->job);
        if (NULL != strStatus) {
            json_AddStringToObject(jsonJobStatus, "POST_STATUS", strStatus);
        }
        json_AddStringToObject(jsonJobStatus, "STATUS_TIME", timebuf);

        if ((content->job_status & JOB_STATUS_DONE)
            || ((content->job_status & JOB_STATUS_EXIT)
                && !(content->reasons & (EXIT_ZOMBIE | EXIT_RERUN | EXIT_KILL_ZOMBIE)))) {
            STATUS_WAIT_T wStatus;

            JOB_WSTATUS(wStatus) = content->exit_code;
            if ((content->job_status & JOB_STATUS_EXIT) && content->exit_code) {

                if (WIFEXITED(wStatus)) {
                    json_AddNumberToObject(jsonJobStatus, "EXIT_CODE", WEXITSTATUS(wStatus));
                } else {
                    json_AddNumberToObject(jsonJobStatus, "EXIT_SIGNAL", WTERMSIG(wStatus));
                }
            }

            if (0 < content->num_execute_node && !FLOAT_EQUAL_ZERO(content->cpu_time)) {
                json_AddNumberToObject(jsonJobStatus, "CPU_TIME", content->cpu_time);
            }
        }

        json_AddItemToObject(jobhist, "JOB_STATUS", jsonJobStatus);

        free_jobrecord(record);

        break;
    }
    case RECORD_TYPE_JOB_SWITCH: // JOB_SWITCH
    {
        record = read_jobswitch(log, request);
        if (NULL == record) {
            break;
        }

        content = record->record_tail;
        if (NULL == content) {
            break;
        }

        json *jsonJobSwitch = json_CreateObject();
        json_AddNumberToObject(jsonJobSwitch, "JOBID", ARRAY_JOBID(record->job->jobid));
        if (0 < ARRAY_INDEX(record->job->jobid)) {
            json_AddNumberToObject(jsonJobSwitch, "JOB_INDEX", ARRAY_INDEX(record->job->jobid));
        }

        json_AddStringToObject(jsonJobSwitch, "SWITCH_TIME", timebuf);
        json_AddStringToObject(jsonJobSwitch, "TO_QUEUE", content->queue);
        json_AddStringToObject(jsonJobSwitch, "SWITCH_USER", content->user_name);

        json_AddItemToObject(jobhist, "JOB_SWITCH", jsonJobSwitch);

        break;
    }
    case RECORD_TYPE_JOB_MOVE: // JOB_MOVE
    {
        record = read_jobmove(log, request);
        if (NULL == record) {
            break;
        }

        content = record->record_tail;
        if (NULL == content) {
            break;
        }

        json *jsonJobMove = json_CreateObject();
        json_AddNumberToObject(jsonJobMove, "JOBID", ARRAY_JOBID(record->job->jobid));
        if (0 < ARRAY_INDEX(record->job->jobid)) {
            json_AddNumberToObject(jsonJobMove, "JOB_INDEX", ARRAY_INDEX(record->job->jobid));
        }

        json_AddStringToObject(jsonJobMove, "MOVE_TIME", timebuf);
        json_AddStringToObject(jsonJobMove, "MOVE_USER", content->user_name);
        json_AddStringToObject(jsonJobMove, "MOVE_TYPE", content->base==1?"TOP":"BOTTOM");
        json_AddNumberToObject(jsonJobMove, "POSITION", content->position);

        json_AddItemToObject(jobhist, "JOB_MOVE", jsonJobMove);

        break;
    }
    case RECORD_TYPE_JOB_MIGRATE: // JOB_MIGRATE
    {
        record = read_jobmigrate(log, request);
        if (NULL == record) {
            break;
        }

        content = record->record_tail;
        if (NULL == content) {
            break;
        }

        json *jsonJobMigrate = json_CreateObject();
        json_AddNumberToObject(jsonJobMigrate, "JOBID", ARRAY_JOBID(record->job->jobid));
        if (0 < ARRAY_INDEX(record->job->jobid)) {
            json_AddNumberToObject(jsonJobMigrate, "JOB_INDEX", ARRAY_INDEX(record->job->jobid));
        }

        json_AddStringToObject(jsonJobMigrate, "MIG_TIME", timebuf);
        json_AddStringToObject(jsonJobMigrate, "MIG_USER", content->user_name);
        s2j_json_set_array_element(jsonJobMigrate, content, string,
                                   "MIG_SPEC_NODES", spec_nodes, content->num_spec_node);

        json_AddItemToObject(jobhist, "JOB_MIGRATE", jsonJobMigrate);

        break;
    }
    case RECORD_TYPE_JOB_SIGNAL: // JOB_SIGNAL
    {
        record = read_jobsignal(log, request);
        if (NULL == record) {
            break;
        }

        content = record->record_tail;
        if (NULL == content) {
            break;
        }

        json *jsonJobSignal = json_CreateObject();
        json_AddNumberToObject(jsonJobSignal, "JOBID", ARRAY_JOBID(record->job->jobid));
        if (0 < ARRAY_INDEX(record->job->jobid)) {
            json_AddNumberToObject(jsonJobSignal, "JOB_INDEX", ARRAY_INDEX(record->job->jobid));
        }

        json_AddStringToObject(jsonJobSignal, "SIGNAL_TIME", timebuf);
        json_AddStringToObject(jsonJobSignal, "SIGNAL_SYMBOL", content->signal_symbol);
        json_AddStringToObject(jsonJobSignal, "SIGNAL_USER", content->user_name);

        json_AddItemToObject(jobhist, "JOB_SIGNAL", jsonJobSignal);

        break;
    }
    case RECORD_TYPE_JOB_REQUEUE: // JOB_REQUEUE
    {
        record = read_jobrequeue(log, request);
        if (NULL == record) {
            break;
        }

        json *jsonJobRequeue = json_CreateObject();
        json_AddNumberToObject(jsonJobRequeue, "JOBID", ARRAY_JOBID(record->job->jobid));
        if (0 < ARRAY_INDEX(record->job->jobid)) {
            json_AddNumberToObject(jsonJobRequeue, "JOB_INDEX", ARRAY_INDEX(record->job->jobid));
        }

        json_AddStringToObject(jsonJobRequeue, "REQUEUE_TIME", timebuf);

        json_AddItemToObject(jobhist, "JOB_REQUEUE", jsonJobRequeue);

        break;
    }
    case RECORD_TYPE_JOB_SIGACT: // JOB_SIGACT
    {
        record = read_jobsigact(log, request);
        if (NULL == record) {
            break;
        }

        content = record->record_tail;
        if (NULL == content) {
            break;
        }

        json *jsonJobSigAct = json_CreateObject();
        json_AddNumberToObject(jsonJobSigAct, "JOBID", ARRAY_JOBID(record->job->jobid));
        if (0 < ARRAY_INDEX(record->job->jobid)) {
            json_AddNumberToObject(jsonJobSigAct, "JOB_INDEX", ARRAY_INDEX(record->job->jobid));
        }

        if (0 == strcmp(content->signal_symbol, "CHKPNT")) {

            bool_t display = is_chkpnt_job(content, record);

            if (IS_JOB_PENDING(content->job_status)) {

                if (content->chkpnt_period) {
                    json_AddNumberToObject(jsonJobSigAct, "CHKPNT_PERIOD", content->chkpnt_period);
                }
            } else {

                if (content->action_status == ACT_START) {

                    if (display) {
                        json_AddStringToObject(jsonJobSigAct, "CHKPNT_START_TIME", timebuf);
                        json_AddNumberToObject(jsonJobSigAct, "CHKPNT_PERIOD", content->chkpnt_period);

                        if (content->act_flags & QP_CHKPNT_MIG) {
                            json_AddTrueToObject(jsonJobSigAct, "MIG_CHKPNT");
                        } else {
                            json_AddTrueToObject(jsonJobSigAct, "CHKPNT");
                        }

                        json_AddNumberToObject(jsonJobSigAct, "CHKPNT_ACTPID", content->action_pid);
                    } else {
                        json_AddStringToObject(jsonJobSigAct, "REQUEUE_TIME", timebuf);
                    }
                } else if (content->action_status == ACT_DONE || content->action_status == ACT_FAIL) {

                    if (display) {
                        json_AddStringToObject(jsonJobSigAct, "CHKPNT_FINISH_TIME", timebuf);

                        if (content->act_flags & QP_CHKPNT_MIG) {
                            json_AddTrueToObject(jsonJobSigAct, "MIG_CHKPNT");
                        } else {
                            json_AddTrueToObject(jsonJobSigAct, "CHKPNT");
                        }

                        if (content->action_status == ACT_DONE) {
                            json_AddTrueToObject(jsonJobSigAct, "CHKPNT_SUCCEEDED");
                        } else {
                            json_AddTrueToObject(jsonJobSigAct, "CHKPNT_FAILED");
                        }

                        json_AddNumberToObject(jsonJobSigAct, "CHKPNT_ACTPID", content->action_pid);
                    } else {
                        json_AddStringToObject(jsonJobSigAct, "REQUEUE_TIME", timebuf);
                        json_AddTrueToObject(jsonJobSigAct, "REQUEUE_DONE");
                    }
                }
            }
        } else {
            switch(content->action_status) {
            case ACT_START:

                json_AddStringToObject(jsonJobSigAct, "SIGACT_TIME", timebuf);
                json_AddStringToObject(jsonJobSigAct, "SIGNAL_SYMBOL", content->signal_symbol);
                json_AddNumberToObject(jsonJobSigAct, "SIGNAL_ACTPID", content->action_pid);
                break;
            case ACT_DONE:
            case ACT_FAIL:

                json_AddStringToObject(jsonJobSigAct, "SIGACT_TIME", timebuf);
                json_AddStringToObject(jsonJobSigAct, "SIGNAL_SYMBOL", content->signal_symbol);
                json_AddNumberToObject(jsonJobSigAct, "SIGNAL_ACTPID", content->action_pid);

                if (content->action_status == ACT_DONE) {
                    json_AddTrueToObject(jsonJobSigAct, "SIGACT_SUCCEEDED");
                } else {
                    json_AddTrueToObject(jsonJobSigAct, "SIGACT_FAILED");
                }
                break;
            }
        }

        json_AddItemToObject(jobhist, "JOB_SIGACT", jsonJobSigAct);

        break;
    }
    case RECORD_TYPE_JOB_START_ACCEPT: // JOB_START_ACCEPT
    {
        record = read_jobstarting(log, request);
        if (NULL == record) {
            break;
        }

        content = record->record_tail;
        if (NULL == content) {
            break;
        }

        json *jsonJobAccept = json_CreateObject();
        json_AddNumberToObject(jsonJobAccept, "JOBID", ARRAY_JOBID(record->job->jobid));
        if (0 < ARRAY_INDEX(record->job->jobid)) {
            json_AddNumberToObject(jsonJobAccept, "JOB_INDEX", ARRAY_INDEX(record->job->jobid));
        }

        json_AddStringToObject(jsonJobAccept, "ACCEPT_TIME", timebuf);
        json_AddNumberToObject(jsonJobAccept, "JOB_PID", content->job_pid);

        json_AddItemToObject(jobhist, "JOB_START_ACCEPT", jsonJobAccept);

        break;
    }
    case RECORD_TYPE_JOB_CLEAN: // JOB_CLEAN
    {
        record = read_jobclean(log, request);
        if (NULL == record) {
            break;
        }

        json *jsonJobClean = json_CreateObject();
        json_AddNumberToObject(jsonJobClean, "JOBID", ARRAY_JOBID(record->job->jobid));
        if (0 < ARRAY_INDEX(record->job->jobid)) {
            json_AddNumberToObject(jsonJobClean, "JOB_INDEX", ARRAY_INDEX(record->job->jobid));
        }

        json_AddStringToObject(jsonJobClean, "CLEAN_TIME", timebuf);

        json_AddItemToObject(jobhist, "JOB_CLEAN", jsonJobClean);

        break;
    }
    case RECORD_TYPE_JOB_FORCE: // JOB_FORCE
    {
        record = read_jobforcerun(log, request);
        if (NULL == record) {
            break;
        }

        content = record->record_tail;
        if (NULL == content) {
            break;
        }

        json *jsonJobForce = json_CreateObject();
        json_AddNumberToObject(jsonJobForce, "JOBID", ARRAY_JOBID(record->job->jobid));
        if (0 < ARRAY_INDEX(record->job->jobid)) {
            json_AddNumberToObject(jsonJobForce, "JOB_INDEX", ARRAY_INDEX(record->job->jobid));
        }

        json_AddStringToObject(jsonJobForce, "FORCE_TIME", timebuf);
        json_AddStringToObject(jsonJobForce, "USER_NAME", content->user_name);

        json_AddItemToObject(jobhist, "JOB_FORCE", jsonJobForce);

        break;
    }
    case RECORD_TYPE_JOB_UNFULFILL:
    case RECORD_TYPE_JOB_FINISH:
    case RECORD_TYPE_JM_JOB_STATUS:
    case RECORD_TYPE_ADDNODE:
    case RECORD_TYPE_DELNODE:
    case RECORD_TYPE_TRAINING_RESULT:
        // These type of records are not logged into qp.record file.
        break;

    default: // should not go here as we already checked the record type.
        break;
    }

    return;
} // end function trans_record_2json

/**
 * @brief       read record files according to the request.
 *
 * @param[in]   request       #1: job hist request
 *
 * @note        if -f is specified, read the specified file;
 *              if -n is specified, read the record files from max to min;
 *              if -T is specified, read the record files between the begin time and end time;
 *              otherwise, read the current working record file
 *
 *              if -t is specified, we read and display record one by one.
 */
static void
read_record_files(struct recordRequest *request)
{
    char strWorkDir[PATH_MAX+1];
    snprintf(strWorkDir, PATH_MAX, "%s", baseParams[BASE_WORKDIR].string_value);

    struct recordFileHandle recHandle;
    memset(&recHandle, 0, sizeof(struct recordFileHandle));

    if (request->options & OPT_ELOGFILE) {

        // -f is specified, open the specified file.
        recHandle.file_handle = fopen(request->record_file_name, "r");
        if (NULL == recHandle.file_handle) {

            fprintf(stderr, STR_FUNC_S_FAIL_M, __func__, "fopen", request->record_file_name);
            exit(3);
        }

        strncpy(recHandle.record_filename, request->record_file_name, PATH_MAX);
        recHandle.current_fileno = 0;
        recHandle.last_fileno = -1;
    } else if (request->options & OPT_NUMLOGFILE) {

        // -n is specifed, get the maximum record file.
        char strRecordFile[PATH_MAX+1];
        unsigned int maxRecordFile = 0;
        struct stat st;

        do {
            snprintf(strRecordFile, PATH_MAX, "%s/qp.records.%d",
                     baseParams[BASE_WORKDIR].string_value, ++maxRecordFile);
        } while (stat(strRecordFile, &st) == 0);

        if (request->num_max_file >= maxRecordFile) {
            if (request->num_min_file >= maxRecordFile)
                request->num_min_file = 0;

            recHandle.current_fileno = maxRecordFile - 1;
	} else {
            recHandle.current_fileno = request->num_max_file - 1;
        }
	recHandle.last_fileno = request->num_min_file;

        if (0 == recHandle.current_fileno) {
            snprintf(recHandle.record_filename, PATH_MAX, "%s/qp.records", strWorkDir);
        } else {
            snprintf(recHandle.record_filename, PATH_MAX,"%s/qp.records.%d",
                    strWorkDir, recHandle.current_fileno);
        }

        recHandle.file_handle = fopen(recHandle.record_filename, "r");
        if (NULL == recHandle.file_handle) {

            fprintf(stderr, STR_FUNC_S_FAIL_M, __func__, "fopen", recHandle.record_filename);
            exit(3);
        }
    } else if (request->search_time[1] == -1) {

        // no -f, -n, -T specified, open the current working record file.
        recHandle.current_fileno = 0;
        recHandle.last_fileno = 0;
        snprintf(recHandle.record_filename, PATH_MAX, "%s/qp.records", strWorkDir);

        recHandle.file_handle = fopen(recHandle.record_filename, "r");
        if (NULL == recHandle.file_handle) {

            fprintf(stderr, STR_FUNC_S_FAIL_M, __func__, "fopen", recHandle.record_filename);
            exit(3);
        }
    } else {

        // -T specified, open the record file which log time is between begin time and end time
        if (request->options & OPT_JOBID) {
            request->search_time[1] = -1;
        }

        int ret;
        ret = get_record_handle(request->search_time, &recHandle);
        if (0 > ret) {
            exit(3);
	}
    }

    if (request->options & OPT_CHRONICLE) {

        // -t is specifed, read record files and display record one by one.
        json *jsonChronic = json_CreateObject();

        json *jsonJob = json_CreateObject();
        while (TRUE) {

            struct record *log;
            log = read_record_fromhandle(&recHandle);
            if (NULL == log) {

                if (quickpool_errno == ERROR_EOF) { // read to the end of the file
                    break;
                }

                continue;
            }

            if ((-1 == request->search_time[1])
                || (log->log_time >= request->search_time[0]
                    && log->log_time <= request->search_time[1])) {

                // -T not specified or the log time is between the begin time and the end time.
                trans_record_2json(log, request, jsonJob);
                sboolFoundJobInRecord = TRUE;
            }
        }

        if (sboolFoundJobInRecord) {
            json_AddItemToObject(jsonChronic, "JOB_HISTORY", jsonJob);
        }

        char *strJson;
        strJson = json_Print(jsonChronic);
        if (NULL != strJson) {
            fprintf(stdout, "%s\n", strJson);
            FREEUP(strJson);
        } else {

            perror("malloc");
            json_Delete(jsonChronic);
            fclose(recHandle.file_handle);

            exit(4);
        }
        json_Delete(jsonChronic);

        fclose(recHandle.file_handle);

        return;
    }

    // not -t case, just read job record from record files and put them into a hash.
    while (TRUE) {

        struct record *log;
        log = get_next_record_4job(&recHandle, request->num_job, request->job_ids);
        if (NULL == log) {

            if (quickpool_errno == ERROR_EOF) {
                break;
            }

            continue;
        }

        if ((-1 == request->search_time[1])
            || (log->log_time >= request->search_time[0]
                && log->log_time <= request->search_time[1])) {

            // -T not specified or the log time is between the begin time and the end time.
            parse_record(log, request);
        }
    }

    fclose(recHandle.file_handle);

    return;
} // end function read_record_files

/**
 * @brief       transfer system record to json format.
 *
 * @param[in]   log           #1: record log to be transferred
 * @param[in]   req           #2: request rule to do the transfer
 * @param[out]  jsonhist      #3: json format of the system record
 *
 */
static void
trans_histrecord_2json(struct record *log, struct histReq *req, json *jsonhist)
{
    if (req->log_time[1] != 0 && req->log_time[0] < req->log_time[1]) {
        if (log->log_time < req->log_time[0] || log->log_time > req->log_time[1]) {
            // record time is not satisfied.
            return;
        }
    }

    char timebuf[TIMESTRINGLEN];
    ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &log->log_time, timebuf);

    switch (log->type) {
    case RECORD_TYPE_LOG_SWITCH: // switch record.
        if (req->request_code == JS_HIST || req->request_code == SYS_HIST) {

            unsigned int i;
            JOBID_T lastjobid=0;
            for (i=0; i<log->numKVs; i++) {
                switch (log->KVs[i].key) {
                case RECORD_KEY_JOBID:
                    lastjobid = atoll(log->KVs[i].value);
                    break;
                }
            }

            json *jsonLogSwitch = json_CreateObject();
            json_AddStringToObject(jsonLogSwitch, "SWITCH_TIME", timebuf);

            json_AddNumberToObject(jsonLogSwitch, "LAST_JOBID", ARRAY_JOBID(lastjobid));
            if (0 < ARRAY_INDEX(lastjobid)) {
                json_AddNumberToObject(jsonLogSwitch, "LAST_JOBID_INDEX", ARRAY_INDEX(lastjobid));
            }

            json_AddItemToObject(jsonhist, "LOG_SWITCH", jsonLogSwitch);
        }

        break;
    case RECORD_TYPE_JS_START: // js start record.
	if (req->request_code == JS_HIST || req->request_code == SYS_HIST) {

            unsigned int i;
            char *master=NULL, *cluster=NULL;
            unsigned int numhosts=0, numqueues=0;
            for (i=0; i<log->numKVs; i++) {
                switch (log->KVs[i].key) {
                case RECORD_KEY_MANAGER_NODE:
                    master = log->KVs[i].value;
                    break;
                case RECORD_KEY_CLUSTER_NAME:
                    cluster = log->KVs[i].value;
                    break;
                case RECORD_KEY_NUM_QUEUE:
                    numqueues = atoi(log->KVs[i].value);
                    break;
                case RECORD_KEY_NUM_HOST:
                    numhosts = atoi(log->KVs[i].value);
                    break;
                }
            }

            json *jsonJsStart = json_CreateObject();
            json_AddStringToObject(jsonJsStart, "JS_START_TIME", timebuf);
            if (NULL != master) {
                json_AddStringToObject(jsonJsStart, "MASTER_HOST", master);
            }
            if (NULL != cluster) {
                json_AddStringToObject(jsonJsStart, "CLUSTER_NAME", cluster);
            }
            json_AddNumberToObject(jsonJsStart, "NUM_SERVER", numhosts);
            json_AddNumberToObject(jsonJsStart, "NUM_QUEUE", numqueues);

            json_AddItemToObject(jsonhist, "JS_START", jsonJsStart);
	}

        break;
    case RECORD_TYPE_JS_DIE: // js die record.
	if (req->request_code == JS_HIST || req->request_code == SYS_HIST) {

            unsigned int i;
            char *master=NULL;
            unsigned int exitcode=0;
            for (i=0; i<log->numKVs; i++) {
                switch (log->KVs[i].key) {
                case RECORD_KEY_MANAGER_NODE:
                    master = log->KVs[i].value;
                    break;
                case RECORD_KEY_EXIT_CODE:
                    exitcode = atoi(log->KVs[i].value);
                    break;
                }
            }

            json *jsonJsDie = json_CreateObject();

            json_AddStringToObject(jsonJsDie, "JS_DIE_TIME", timebuf);
            if (NULL != master) {
                json_AddStringToObject(jsonJsDie, "MASTER_HOST", master);
            }

            char strExitCode[12];
            switch (exitcode) {
            case JS_QUIT_RESIGN:
                json_AddStringToObject(jsonJsDie, "DIE_REASON", "MASTER_RESIGN");
                break;
            case JS_QUIT_RECFILE:
                json_AddStringToObject(jsonJsDie, "DIE_REASON", "MASTER_RECORD_FILE");
                break;
            case DAEMON_QUIT_RECONFIG:
                json_AddStringToObject(jsonJsDie, "DIE_REASON", "MASTER_RECONFIG");
                break;
            case DAEMON_QUIT_CONFFILE:
                json_AddStringToObject(jsonJsDie, "DIE_REASON", "MASTER_CONF_FILE");
                break;
            case DAEMON_QUIT_FATAL:
                json_AddStringToObject(jsonJsDie, "DIE_REASON", "MASTER_FATAL");
                break;
            case DAEMON_QUIT_MEM:
                json_AddStringToObject(jsonJsDie, "DIE_REASON", "MASTER_MEMORY");
                break;
            default:
                snprintf(strExitCode, 12, "%d", exitcode);
                json_AddStringToObject(jsonJsDie, "DIE_REASON", strExitCode);
                break;
            }

            json_AddItemToObject(jsonhist, "JS_DIE", jsonJsDie);
        }

        break;
    case RECORD_TYPE_QUEUE_CTRL: // manage queue record
        if ((req->request_code == SYS_HIST) || (req->request_code == QUEUE_HIST)) {
            unsigned int i;
            char *queue=NULL, *username=NULL;
            unsigned int reqcode=0;

            for (i=0; i<log->numKVs; i++) {
                switch (log->KVs[i].key) {
                case RECORD_KEY_REQCODE:
                    reqcode = atoi(log->KVs[i].value);
                    break;
                case RECORD_KEY_QUEUE:
                    queue = log->KVs[i].value;
                    break;
                case RECORD_KEY_USERNAME:
                    username = log->KVs[i].value;
                    break;
                }
            }

            if (NULL == queue) { // should have a queue name.
                break;
            }

            if (req->num_name != 0 && !has_name_inlist(queue, req->names, req->num_name)) {
                // not the request queue, ignore it.
                break;
            }

            json *jsonQueueCtrl = json_CreateObject();

            json_AddStringToObject(jsonQueueCtrl, "OPERATE_TIME", timebuf);
            json_AddStringToObject(jsonQueueCtrl, "QUEUE_NAME", queue);

            switch (reqcode) {
            case QUEUE_OPEN:
                json_AddStringToObject(jsonQueueCtrl, "OPERATE_TYPE", "QUEUE_OPEN");
                break;
            case QUEUE_CLOSED:
                json_AddStringToObject(jsonQueueCtrl, "OPERATE_TYPE", "QUEUE_CLOSE");
                break;
	    case QUEUE_ACTIVATE:
                json_AddStringToObject(jsonQueueCtrl, "OPERATE_TYPE", "QUEUE_ACTIVE");
                break;
            case QUEUE_INACTIVATE:
                json_AddStringToObject(jsonQueueCtrl, "OPERATE_TYPE", "QUEUE_INACTIVE");
                break;
            default:
                json_AddStringToObject(jsonQueueCtrl, "OPERATE_TYPE", "UNKNOWN");
                break;
	    }

            if (NULL != username && '\0' != username[0]) {
                json_AddStringToObject(jsonQueueCtrl, "OPERATE_USER", username);
            }

            json_AddItemToObject(jsonhist, "QUEUE_CONTROL", jsonQueueCtrl);
        }

	break;
    case RECORD_TYPE_HOST_CTRL: // manage node record.
        if ((req->request_code == SYS_HIST) || (req->request_code == HOST_HIST)) {
            unsigned int i;
            char *host=NULL, *username=NULL;
            unsigned int reqcode=0;

            for (i=0; i<log->numKVs; i++) {
                switch (log->KVs[i].key) {
                case RECORD_KEY_REQCODE:
                    reqcode = atoi(log->KVs[i].value);
                    break;
                case RECORD_KEY_HOST_NAME:
                    host = log->KVs[i].value;
                    break;
                case RECORD_KEY_USERNAME:
                    username = log->KVs[i].value;
                    break;
                }
            }

            if (NULL == host) { // should have a node name.
                break;
            }

            if (req->num_name != 0 && !has_name_inlist(host, req->names, req->num_name)) {
                // not the request node, ignore it.
                break;
            }

            json *jsonHostCtrl = json_CreateObject();

            json_AddStringToObject(jsonHostCtrl, "OPERATE_TIME", timebuf);
            json_AddStringToObject(jsonHostCtrl, "HOST_NAME", host);

            switch (reqcode) {
            case HOST_OPEN:
                json_AddStringToObject(jsonHostCtrl, "OPERATE_TYPE", "HOST_OPEN");
                break;
            case HOST_CLOSE:
                json_AddStringToObject(jsonHostCtrl, "OPERATE_TYPE", "HOST_CLOSE");
                break;
            case HOST_REBOOT:
                json_AddStringToObject(jsonHostCtrl, "OPERATE_TYPE", "JM_RESTART");
                break;
            case HOST_SHUTDOWN:
                json_AddStringToObject(jsonHostCtrl, "OPERATE_TYPE", "JM_SHUTDOWN");
                break;
            default:
                json_AddStringToObject(jsonHostCtrl, "OPERATE_TYPE", "UNKNOWN");
                break;
            }

            if (NULL != username && '\0' != username[0]) {
                json_AddStringToObject(jsonHostCtrl, "OPERATE_USER", username);
            }

            json_AddItemToObject(jsonhist, "HOST_CONTROL", jsonHostCtrl);
	}

        break;
    default:
        // do nothing for other type of record.
	break;
    }

    return;
} // end function trans_histrecord_2json

/**
 * @brief       check if the job should be ignored according to the request.
 *
 * @param[in]   record        #1: record to be checked
 * @param[in]   request       #2: request rule to bypass the record
 *
 * @retval      TRUE          #1: bypass the job record
 * @retval      FALSE         #2: should display the job record
 */
static bool_t
skip_job(struct recordJob *record, struct recordRequest *request)
{
    int options;
    options = request->options;

    if (!is_request_jobid(request, record->job->jobid)) {
        return TRUE;
    }

    if (((options & OPT_ALLUSERS) != OPT_ALLUSERS)
        && (strcmp(record->job->submit->user, request->user_name))) {
        return TRUE;
    }

    if (((options & OPT_ALLPROJ) != OPT_ALLPROJ)
        && (strcmp(record->job->submit->project, request->project_name))) {
        return TRUE;
    }

    if (options & OPT_ARRAY_INFO) {
        if (strchr(record->job->submit->job_name, '[') == NULL)
            return TRUE;
        else
            return FALSE;
    }

    if ((options & OPT_ALL) != OPT_ALL && !(options & OPT_CHRONICLE)) {
        if ((options & OPT_DFTSTATUS) != OPT_DFTSTATUS) {
            if (!(((options & OPT_DONE) && (record->current_status & JOB_STATUS_DONE))
                  || ((options & OPT_DONE) && (record->current_status & JOB_STATUS_EXIT))
                  || ((options & OPT_EXIT) && (record->current_status & JOB_STATUS_EXIT))
                  || ((options & OPT_PEND) && (IS_JOB_PENDING(record->current_status)))
                  || ((options & OPT_SUSP) && (record->current_status
                                               & (JOB_STATUS_SSUSP | JOB_STATUS_USUSP)))
                  || ((options & OPT_RUN) && (record->current_status & JOB_STATUS_RUN)))) {

                return TRUE;
            }
        } else {
            if (record->current_status & (JOB_STATUS_DONE |JOB_STATUS_EXIT)) {
	        return TRUE;
            }
        }
    }

    if ((options & OPT_QUEUE)
        && (strcmp(record->job->submit->queue, request->queue) != 0)) {

        return TRUE;
    }

    if (options & OPT_HOST) {
        if (record->job->start_time == 0) return TRUE;

        unsigned int i;
        bool_t found=FALSE;
        for (i=0; i < record->job->num_execute_node; i++) {
	    if (strcmp(record->job->execute_nodes[i], request->node) == 0) {
	        found = TRUE;
	        break;
            }
        }

        if (!found) {
            return TRUE;
        }
    }

    if (options & OPT_COMPLETE) {
        if (record->job->end_time == 0) return TRUE;

        if ((record->job->end_time < request->end_time[0])
            || (record->job->end_time > request->end_time[1])) {
	    return TRUE;
        }
    }

    if (options & OPT_SUBMIT) {
        if (record->job->submit->submit_time == 0) return TRUE;

        if ((record->job->submit->submit_time < request->submit_time[0])
            || (record->job->submit->submit_time > request->submit_time[1])) {
	    return TRUE;
        }
    }

    if (options & OPT_DISPATCH) {
        if (record->job->start_time == 0) return TRUE;

        if ((record->job->start_time < request->start_time[0])
            || (record->job->start_time > request->start_time[1])) {
	    return TRUE;
        }
    }

    return FALSE;
} // end function skip_job

/**
 * @brief       transfer job record to json and display it.
 *
 * @param[in]   request       #1: request rule filter the job record.
 *
 */
static void
display_job_record(struct recordRequest *request)
{
    time_t now = time(NULL);

    json *jsonJobList = json_CreateObject();
    unsigned int i;
    struct recordJob *record;
    for (record = (struct recordJob*)glistJobRecord->forw;
         record != (struct recordJob*)glistJobRecord;
         record = (struct recordJob*)record->forw) {

        if (skip_job(record, request)) {
            // job is not satified with the request.
            continue;
        }
	sboolFoundJobInRecord = TRUE;

        struct QuickPoolJobInfo *job;
        job = record->job;

        json *jsonJob = json_CreateObject();

        json_AddNumberToObject(jsonJob, "JOBID", ARRAY_JOBID(job->jobid));
        if (0 < ARRAY_INDEX(job->jobid)) {
            json_AddNumberToObject(jsonJob, "JOBID_INDEX", ARRAY_INDEX(job->jobid));
        }

        json *jsonJobSubmit = submitrequest_2json(job);
        json_AddItemToObject(jsonJob, "JOB_NEW", jsonJobSubmit);

        char *currentOrDoneTime = NULL;
        long int pendTime=0, pendSuspTime=0, runTime=0, sysSuspTime=0, usrSuspTime=0;
        long int unknownTime=0;
        time_t nextTimeStamp=0;
        int laststatus=0;

        unsigned int numContent=0;
        struct recordContent *content, *nextContent;
        content = record->record_head;
        while (content) {

            nextContent = content->next;

            while ((nextContent != NULL) && ((nextContent->kind == RECORD_TYPE_JOB_SWITCH))) {
                nextContent = nextContent->next;
            }

            time_t timeStamp;
            if (nextTimeStamp > 0) {
                timeStamp = nextTimeStamp;
            } else {
                timeStamp = content->time_stamp;
            }

            char timebuf[TIMESTRINGLEN];
            ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &timeStamp, timebuf);

            char strTimeStamp[TIMESTRINGLEN];
            ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &timeStamp, strTimeStamp);

            if (is_request_jobid(request, job->jobid)) {

                if (RECORD_TYPE_JOB_CLEAN != content->kind) {
                    job->status = content->job_status;
                }

                switch (content->kind) {
                case RECORD_TYPE_PRE_EXEC_START:
                case RECORD_TYPE_JOB_START:
                {
                    json *jsonJobStart = json_CreateObject();

                    json_AddStringToObject(jsonJobStart, "START_TIME", strTimeStamp);

                    int numAllocSlot=0;
                    json *jsonNodeArray = json_CreateArray();
                    for (i=0; i<content->num_execute_node; i++) {
                        char strExecNode[PATH_MAX+1];

                        snprintf(strExecNode, PATH_MAX, "%d*%s",
                                 content->num_slot[i], content->execute_nodes[i]);
                        json_AddItemToArray(jsonNodeArray, json_CreateString(strExecNode));
                        numAllocSlot += content->num_slot[i];
                    }
                    json_AddNumberToObject(jsonJobStart, "NUM_ALLOCATE_SLOT", numAllocSlot);
                    json_AddNumberToObject(jsonJobStart, "NUM_EXECUTE_NODE",
                                           content->num_execute_node);
                    json_AddItemToObject(jsonJobStart, "EXECUTE_NODE", jsonNodeArray);

                    if (content->kind == RECORD_TYPE_PRE_EXEC_START) {
                        json_AddItemToObject(jsonJob, "PRE_EXEC_START", jsonJobStart);
                    } else {
                        json_AddItemToObject(jsonJob, "JOB_START", jsonJobStart);
                    }

                    break;
                }
                case RECORD_TYPE_JOB_START_ACCEPT:
                {
                    json *jsonJobAccept = json_CreateObject();
                    json_AddStringToObject(jsonJobAccept, "ACCEPT_TIME", strTimeStamp);
                    json_AddNumberToObject(jsonJobAccept, "JOB_PID", content->job_pid);

                    json_AddItemToObject(jsonJob, "JOB_START_ACCEPT", jsonJobAccept);
                    break;
                }
                case RECORD_TYPE_JOB_EXECUTE:
                {
                    json *jsonJobExecute = json_CreateObject();
                    json_AddStringToObject(jsonJobExecute, "EXECUTE_TIME", strTimeStamp);
                    if (NULL != content->execute_home && '\0' != content->execute_home[0]) {
                        json_AddStringToObject(jsonJobExecute, "EXECUTE_HOMEDIR",
                                               content->execute_home);
                    }

                    if (NULL != content->execute_cwd && '\0' != content->execute_cwd[0]) {
                        json_AddStringToObject(jsonJobExecute, "EXECUTE_CWD", content->execute_cwd);
                    }

                    if (NULL != content->execute_user && '\0' != content->execute_user[0]) {
                        json_AddStringToObject(jsonJobExecute, "EXECUTE_USER", content->execute_user);
                    }

                    if (0 < content->job_pid) {
                        json_AddNumberToObject(jsonJobExecute, "EXECUTE_PID", content->job_pid);
                    }

                    json_AddItemToObject(jsonJob, "JOB_EXECUTE", jsonJobExecute);
                    break;

                }
                case RECORD_TYPE_JOB_STATUS:
                {
                    json *jsonJobStatus = json_CreateObject();

                    const char *strStatus;
                    strStatus = get_status(job);
                    json_AddStringToObject(jsonJobStatus, "STATUS", strStatus);
                    strStatus = get_post_status(job);
                    if (NULL != strStatus) {
                        json_AddStringToObject(jsonJobStatus, "POST_STATUS", strStatus);
                    }
                    json_AddStringToObject(jsonJobStatus, "STATUS_TIME", timebuf);

                    if ((content->job_status & JOB_STATUS_DONE)
                        || ((content->job_status & JOB_STATUS_EXIT)
                            && !(content->reasons & (EXIT_ZOMBIE | EXIT_RERUN | EXIT_KILL_ZOMBIE)))) {

                        STATUS_WAIT_T wStatus;
                        JOB_WSTATUS(wStatus) = content->exit_code;
                        if ((content->job_status & JOB_STATUS_EXIT) && content->exit_code) {

                            if (WIFEXITED(wStatus)) {
                                json_AddNumberToObject(jsonJobStatus, "EXIT_CODE",
                                                       WEXITSTATUS(wStatus));
                            } else {
                                json_AddNumberToObject(jsonJobStatus, "EXIT_SIGNAL",
                                                       WTERMSIG(wStatus));
                            }
                        }

                        if (0 < content->num_execute_node && !FLOAT_EQUAL_ZERO(content->cpu_time)) {
                            json_AddNumberToObject(jsonJobStatus, "CPU_TIME", content->cpu_time);
                        }
                    }

                    json_AddItemToObject(jsonJob, "JOB_STATUS", jsonJobStatus);
                    break;
                }
                case RECORD_TYPE_JOB_FORCE:
                {
                    json *jsonJobForce = json_CreateObject();
                    json_AddStringToObject(jsonJobForce, "FORCE_TIME", strTimeStamp);
                    json_AddStringToObject(jsonJobForce, "USER_NAME", content->user_name);

                    json_AddItemToObject(jsonJob, "JOB_CLEAN", jsonJobForce);
                    break;
                }
                case RECORD_TYPE_JOB_MODIFY:
                {
                    struct QuickPoolJobInfo *modifyJob = content->modify_params;
                    if (NULL == modifyJob) {
                         break;
                    }

                    struct submitRequest *submit = modifyJob->submit;
                    if (NULL == submit) {
                        break;
                    }

                    json *jsonJobModify = json_CreateObject();
                    json_AddStringToObject(jsonJobModify, "MODIFY_TIME", strTimeStamp);

                    json *jsonJobSubmit = submitrequest_2json(modifyJob);
                    json_AddItemToObject(jsonJobModify, "MODIFIED_REQUEST", jsonJobSubmit);

                    bool_t removeOption=FALSE;
                    json *jsonJobRemoveOption = json_CreateArray();
                    if (submit->modn_options & SUBOPT_JOB_NAME) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-J"));
                    }
                    if (submit->modn_options & SUBOPT_QUEUE) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-q"));
                    }
                    if (submit->modn_options & SUBOPT_HOST ) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-m"));
                    }
                    if (submit->modn_options & SUBOPT_IN_FILE) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-i"));
                    }
                    if (submit->modn_options & SUBOPT_OUT_FILE) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-o"));
                    }
                    if (submit->modn_options & SUBOPT_ERR_FILE) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-e"));
                    }
                    if (submit->modn_options & SUBOPT_EXCLUSIVE) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-x"));
                    }
                    if (submit->modn_options & SUBOPT_NOTIFY_END) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-N"));
                    }
                    if (submit->modn_options & SUBOPT_NOTIFY_BEGIN) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-B"));
                    }
                    if (submit->modn_options & SUBOPT_RERUNNABLE) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-r"));
                    }
                    if (submit->modn_options & SUBOPT_DEPEND_COND) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-w"));
                    }
                    if (submit->modn_options & SUBOPT_RES_REQ ) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-R"));
                    }
                    if (submit->modn_options & SUBOPT_OTHER_FILES) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-f"));
                    }
                    if (submit->modn_options & SUBOPT_PRE_EXEC) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-E"));
                    }
                    if (submit->modn_options & SUBOPT_POST_EXEC) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-A"));
                    }
                    if (submit->modn_options & SUBOPT_LOGIN_SHELL) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-L"));
                    }
                    if (submit->modn_options & SUBOPT_MAIL_USER) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-u"));
                    }
                    if (submit->modn_options & SUBOPT_PROJECT_NAME) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-P"));
                    }
                    int j;
                    for (j = 0; j < RESOURCE_LIMIT_NUM; j++) {
                        if (submit->job_ulimit[j] != DELETE_NUMBER) {
                            continue;
                        }

                        removeOption = TRUE;
                        switch(j) {
                        case RESOURCE_LIMIT_CPU:
                            json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-c"));
                            break;
                        case RESOURCE_LIMIT_FSIZE:
                            json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-F"));
                            break;
                        case RESOURCE_LIMIT_DATA:
                            json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-D"));
                            break;
                        case RESOURCE_LIMIT_STACK:
                            json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-S"));
                            break;
                        case RESOURCE_LIMIT_CORE:
                            json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-C"));
                            break;
                        case RESOURCE_LIMIT_RSS:
                            json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-M"));
                            break;
                        case RESOURCE_LIMIT_SWAP:
                            json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-v"));
                            break;
                        case RESOURCE_LIMIT_PROCESS:
                            json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-p"));
                            break;
                        case RESOURCE_LIMIT_RUN:
                            json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-W"));
                            break;
                        }
                    }
                    if (submit->num_job_slot == DEL_NUMPRO) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-n"));
                    }
                    if (submit->begin_time == DELETE_NUMBER) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-b"));
                    }
                    if (submit->terminate_time == DELETE_NUMBER) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-t"));
                    }
                    if (submit->modn_options & SUBOPT_JOB_PRIORITY) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-j"));
                    }
                    if (submit->modn_options & SUBOPT2_REQUEUE_EXITCODE) {
                        removeOption = TRUE;
                        json_AddItemToArray(jsonJobRemoveOption, json_CreateString("-Q"));
                    }
                    if (removeOption) {
                        json_AddItemToObject(jsonJobModify, "REMOVED_REQUEST", jsonJobRemoveOption);
                    } else {
                        json_Delete(jsonJobRemoveOption);
                    }

                    json_AddItemToObject(jsonJob, "JOB_MODIFY", jsonJobModify);
                    break;
                }
                case RECORD_TYPE_JOB_CLEAN:
                {
                    json *jsonJobClean = json_CreateObject();
                    json_AddStringToObject(jsonJobClean, "CLEAN_TIME", strTimeStamp);

                    json_AddItemToObject(jsonJob, "JOB_CLEAN", jsonJobClean);
                    break;
                }
                case RECORD_TYPE_JOB_SIGNAL:
                {
                    json *jsonJobSignal = json_CreateObject();

                    json_AddStringToObject(jsonJobSignal, "SIGNAL_TIME", strTimeStamp);
                    json_AddStringToObject(jsonJobSignal, "SIGNAL_SYMBOL",
                                           content->signal_symbol);
                    json_AddStringToObject(jsonJobSignal, "SIGNAL_USER",
                                           content->user_name);

                    json_AddItemToObject(jsonJob, "JOB_SIGNAL", jsonJobSignal);
                    break;
                }
                case RECORD_TYPE_JOB_SIGACT:
                {
                    json *jsonJobSigAct = json_CreateObject();

                    if (0 == strcmp(content->signal_symbol, "CHKPNT")) {

                        bool_t display = is_chkpnt_job(content, record);

                        if (IS_JOB_PENDING(content->job_status)) {

                            if (content->chkpnt_period) {
                                json_AddNumberToObject(jsonJobSigAct, "CHKPNT_PERIOD",
                                                       content->chkpnt_period);
                            }
                        } else {

                            if (content->action_status == ACT_START) {

                                if (display) {
                                    json_AddStringToObject(jsonJobSigAct, "CHKPNT_START_TIME", timebuf);
                                    json_AddNumberToObject(jsonJobSigAct, "CHKPNT_PERIOD",
                                                           content->chkpnt_period);

                                    if (content->act_flags & QP_CHKPNT_MIG) {
                                        json_AddTrueToObject(jsonJobSigAct, "MIG_CHKPNT");
                                    } else {
                                        json_AddTrueToObject(jsonJobSigAct, "CHKPNT");
                                    }

                                    json_AddNumberToObject(jsonJobSigAct, "CHKPNT_ACTPID",
                                                           content->action_pid);
                                } else {
                                    json_AddStringToObject(jsonJobSigAct, "REQUEUE_TIME", timebuf);
                                }
                            } else if (content->action_status == ACT_DONE
                                       || content->action_status == ACT_FAIL) {

                                if (display) {
                                    json_AddStringToObject(jsonJobSigAct, "CHKPNT_FINISH_TIME", timebuf);

                                    if (content->act_flags & QP_CHKPNT_MIG) {
                                        json_AddTrueToObject(jsonJobSigAct, "MIG_CHKPNT");
                                    } else {
                                        json_AddTrueToObject(jsonJobSigAct, "CHKPNT");
                                    }

                                    if (content->action_status == ACT_DONE) {
                                        json_AddTrueToObject(jsonJobSigAct, "CHKPNT_SUCCEEDED");
                                    } else {
                                        json_AddTrueToObject(jsonJobSigAct, "CHKPNT_FAILED");
                                    }

                                    json_AddNumberToObject(jsonJobSigAct, "CHKPNT_ACTPID",
                                                           content->action_pid);
                                } else {
                                    json_AddStringToObject(jsonJobSigAct, "REQUEUE_TIME", timebuf);
                                    json_AddTrueToObject(jsonJobSigAct, "REQUEUE_DONE");
                                }
                            }
                        }
                    } else {
                        switch (content->action_status) {
                        case ACT_START:

                            json_AddStringToObject(jsonJobSigAct, "SIGACT_TIME", timebuf);
                            json_AddStringToObject(jsonJobSigAct, "SIGNAL_SYMBOL",
                                                   content->signal_symbol);
                            json_AddNumberToObject(jsonJobSigAct, "SIGNAL_ACTPID",
                                                   content->action_pid);
                            break;
                        case ACT_DONE:
                        case ACT_FAIL:

                            json_AddStringToObject(jsonJobSigAct, "SIGACT_TIME", timebuf);
                            json_AddStringToObject(jsonJobSigAct, "SIGNAL_SYMBOL",
                                                   content->signal_symbol);
                            json_AddNumberToObject(jsonJobSigAct, "SIGNAL_ACTPID",
                                                   content->action_pid);

                            if (content->action_status == ACT_DONE) {
                                json_AddTrueToObject(jsonJobSigAct, "SIGACT_SUCCEEDED");
                            } else {
                                json_AddTrueToObject(jsonJobSigAct, "SIGACT_FAILED");
                            }
                            break;
                        }
                    }

                    json_AddItemToObject(jsonJob, "JOB_SIGACT", jsonJobSigAct);
                    break;
                }
                case RECORD_TYPE_JOB_REQUEUE:
                {
                    json *jsonJobRequeue = json_CreateObject();

                    json_AddStringToObject(jsonJobRequeue, "REQUEUE_TIME",
                                           strTimeStamp);

                    json_AddItemToObject(jsonJob, "JOB_REQUEUE", jsonJobRequeue);
                    break;
                }
                case RECORD_TYPE_JOB_SWITCH:
                {
                    json *jsonJobSwitch = json_CreateObject();

                    json_AddStringToObject(jsonJobSwitch, "SWITCH_TIME", strTimeStamp);
                    json_AddStringToObject(jsonJobSwitch, "TO_QUEUE", content->queue);
                    json_AddStringToObject(jsonJobSwitch, "SWITCH_USER",
                                           content->user_name);

                    json_AddItemToObject(jsonJob, "JOB_SWITCH", jsonJobSwitch);
                    break;
                }
                case RECORD_TYPE_JOB_MOVE:
                {
                    json *jsonJobMove = json_CreateObject();

                    json_AddStringToObject(jsonJobMove, "MOVE_TIME", strTimeStamp);
                    json_AddStringToObject(jsonJobMove, "MOVE_USER", content->user_name);
                    json_AddStringToObject(jsonJobMove, "MOVE_TYPE",
                                           content->base==1?"TOP":"BOTTOM");
                    json_AddNumberToObject(jsonJobMove, "POSITION", content->position);

                    json_AddItemToObject(jsonJob, "JOB_MOVE", jsonJobMove);
                    break;
                }
                case RECORD_TYPE_JOB_MIGRATE:
                {
                    json *jsonJobMigrate = json_CreateObject();

                    json_AddStringToObject(jsonJobMigrate, "MIG_TIME", strTimeStamp);
                    json_AddStringToObject(jsonJobMigrate, "MIG_USER", content->user_name);
                    s2j_json_set_array_element(jsonJobMigrate, content, string, "MIG_SPEC_NODES",
                                               spec_nodes, content->num_spec_node);

                    json_AddItemToObject(jsonJob, "JOB_MIGRATE", jsonJobMigrate);
                    break;
                }
                }
            }

            long int timeinterval, stayTime;
            if (NULL == nextContent) {

                if ((content->job_status & JOB_STATUS_DONE)
                    || ((content->job_status & JOB_STATUS_EXIT)
                        && !(content->reasons & (EXIT_ZOMBIE | EXIT_RERUN | EXIT_KILL_ZOMBIE)))) {

                    stayTime = timeStamp - job->submit->submit_time;
                    ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &timeStamp, timebuf);
                    currentOrDoneTime = copy_string(timebuf);
                } else {
	 	    stayTime = now - job->submit->submit_time;

                    if ((request->search_time[1] < time(NULL)) && (request->search_time[1] != -1)) {
                        content->job_status = JOB_STATUS_EXIT;
                    }

		    if (stayTime < 0) {
		        stayTime = pendTime + pendSuspTime + runTime
                                   + sysSuspTime + usrSuspTime + unknownTime;

                        time_t currentTime;
                        currentTime = job->submit->submit_time + stayTime;

                        ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &currentTime, timebuf);
		        currentOrDoneTime = copy_string(timebuf);
		    } else {
                        ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &now, timebuf);
                        currentOrDoneTime = copy_string(timebuf);
                    }
                }

                timeinterval = now - timeStamp;
                timeinterval = timeinterval < 0 ? 0 : timeinterval;
            } else {
                timeinterval = nextContent->time_stamp - timeStamp;

                if (timeinterval < 0) {
	 	    timeinterval = 0;
		    nextTimeStamp = timeStamp;
                } else
	 	    nextTimeStamp = 0;
	    }

            if (!is_request_jobid(request, job->jobid)) {
                goto notmatch;
            }

            if (content->job_status == 0) {
                content->job_status = laststatus;
            }

            if ((content->kind != RECORD_TYPE_JOB_SWITCH)) {

		switch (content->job_status) {
                case JOB_STATUS_PSUSP:
                    pendSuspTime += timeinterval;

                    break;
                case JOB_STATUS_PEND:
                    pendTime += timeinterval;

                    break;
                case JOB_STATUS_RUN:
                    runTime += timeinterval;

                    break;
                case JOB_STATUS_SSUSP:
                    sysSuspTime += timeinterval;

                    break;
                case JOB_STATUS_USUSP:
                    usrSuspTime += timeinterval;

                    break;
                case JOB_STATUS_DONE:
                    if (nextContent
                        && !(nextContent->job_status & (JOB_STATUS_PDONE|JOB_STATUS_PERR))) {
                        pendTime += timeinterval;
                    }

                    break;
                case JOB_STATUS_EXIT:
                    if ((content->reasons & EXIT_RERUN)
                        || (nextContent
                            && !(nextContent->job_status & (JOB_STATUS_PDONE|JOB_STATUS_PERR)))) {
                        pendTime += timeinterval;
                    }

                    if (content->reasons & (EXIT_KILL_ZOMBIE | EXIT_ZOMBIE)) {
                        unknownTime += timeinterval;
                    }

                    break;
                default:
                    if (!(IS_JOB_POST_DONE(content->job_status)
                          || IS_JOB_POST_ERR(content->job_status))) {
                        unknownTime += timeinterval;
                    }

                    break;
		}
	    }
	    laststatus = content->job_status;

            numContent ++;
            if (1000 < numContent) {
                break;
            }

        notmatch:
            content = content->next;
	}

        json *jsonJobTimeSum = json_CreateObject();

        unsigned int totalTime = pendTime>=0?pendTime:0;
        totalTime += pendSuspTime>=0?pendSuspTime:0;
        totalTime += runTime>=0?runTime:0;
        totalTime += usrSuspTime>=0?usrSuspTime:0;
        totalTime += sysSuspTime>=0?sysSuspTime:0;
        totalTime += unknownTime>=0?unknownTime:0;

        json_AddNumberToObject(jsonJobTimeSum, "PENDING_TIME", pendTime>=0?pendTime:0);
        json_AddNumberToObject(jsonJobTimeSum, "PSUSP_TIME", pendSuspTime>=0?pendSuspTime:0);
        json_AddNumberToObject(jsonJobTimeSum, "RUNNING_TIME", runTime>=0?runTime:0);
        json_AddNumberToObject(jsonJobTimeSum, "USUSP_TIME", usrSuspTime>=0?usrSuspTime:0);
        json_AddNumberToObject(jsonJobTimeSum, "SSUSP_TIME", sysSuspTime>=0?sysSuspTime:0);
        json_AddNumberToObject(jsonJobTimeSum, "UNKNOWN_TIME", unknownTime>=0?unknownTime:0);
        json_AddNumberToObject(jsonJobTimeSum, "TOTAL_TIME", totalTime);

        json_AddItemToObject(jsonJob, "JOB_TIME_SUMMARY", jsonJobTimeSum);

        char strJobId[32];
        jobid_2string(job->jobid, strJobId);
        json_AddItemToObject(jsonJobList, strJobId, jsonJob);

        FREEUP(currentOrDoneTime);
    }

    if (sboolFoundJobInRecord) {
        char *strJson;
        strJson = json_Print(jsonJobList);
        if (NULL != strJson) {
            fprintf(stdout, "%s\n", strJson);
            FREEUP(strJson);
        } else {
            perror("malloc");
            json_Delete(jsonJobList);
            exit(4);
        }
    }
    json_Delete(jsonJobList);

    return;
} // end function display_job_record

/**
 * @brief       parse input --number argument and set values to the request.
 *
 * @param[in]   inputstr      #1: argument string of --number option
 * @param[in]   request       #2: set value to this request
 *
 * @note        this function would exit if input argument has syntax error.
 */
static void
set_request_numfile(char *inputstr, struct recordRequest *request)
{
    char *res = strchr(inputstr, ',');
    if (NULL == res) { // specify one value of -n, it is num_max_file.

        request->num_min_file = 0;
        if (!isinteger(inputstr) || (request->num_max_file = atoi(inputstr)) < 0) {

            fprintf(stderr, "Arguement of -n,--number <%s> has syntax error.\n", inputstr);
            fprintf(stderr, "The number of record file must be a positive integer.\n");
            exit(2);
        }
    } else { // both num_min_file and num_max_file are specified.
        int pos;
        char *first, *second;
        pos = res - inputstr;

        first = (char*)calloc(pos+1, sizeof(char));
        if (NULL == first) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", pos+1);
            exit(4);
        }

        second = (char*)calloc(strlen(inputstr)-pos, sizeof(char));
        if (NULL == second) {
            fprintf(stderr, "calloc(%ld) failed due to %m.\n", strlen(inputstr)-pos);
            exit(4);
        }

        strncpy(first, (const char *) inputstr, (size_t) pos);
        first[pos] = '\0';
        strcpy(second, (const char *) ++res);

        if (!isinteger(first) || !isinteger(second)
            || (request->num_min_file=atoi(first)) < 0 || (request->num_max_file=atoi(second)) < 0) {

            fprintf(stderr, "Arguement of -n,--number <%s> has syntax error.\n", inputstr);
            fprintf(stderr, "The numbers of both record files must be positive integers.\n");
            exit(2);
        }

        if (request->num_min_file > request->num_max_file) {

            fprintf(stderr, "Arguement of -n,--number <%s> has syntax error.\n", inputstr);
            fprintf(stderr, "The value of min_logfile should not exceed the value of max_logfile.\n");
            exit(2);
        }

        free(first);
        free(second);
    }


    if (0 < request->num_min_file) {
        ++request->num_max_file;
    }

    return;
} // end function set_request_numfile

/**
 * @brief       parse input argument and set both begin time and end time to the request.
 *
 * @param[in]   arg           #1: argument string of -t, -C, -D or -S
 * @param[in]   request       #2: set begin time and end time
 *
 * @retval      0             #1: succeed
 * @retval      -1            #2: input argument has syntax error
 */
static int
set_request_times(char *arg, time_t times[])
{
    if (NULL == arg || 0 == strlen(arg)) {
        log_message(ERR, NOCLASS, "%s: input argument is NULL.", __func__);
        return -1;
    }

    char *sp = arg;
    unsigned int numTime = 0;
    unsigned int len = strlen(arg);
    char *strTime = (char *)calloc(len+1, sizeof(char));
    if (NULL == strTime) {
        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc", len+1);
        return -1;
    }

    char *token;
    while ((token = get_string_token(&sp, ",", strTime, len+1)) != NULL) {
        len = strlen(sp);
        numTime ++;
    }

    if (2 < numTime) {
        log_message(ERR, NOCLASS, "%s: specified too many \",\", format is \"time0,time1\".",
                    __func__);
        FREEUP(strTime);
        return -1;
    }

    time_t beginTime=0, endTime=0;
    numTime = 0;
    sp = arg;
    len = strlen(arg);
    while ((token = get_string_token(&sp, ",", strTime, len+1)) != NULL) {

        len = strlen(sp);
        if (0 == numTime) {

            beginTime = optarg_2time(token); 
            if (0 > beginTime) {
                FREEUP(strTime);
                return -1;
            }
        } else {
            endTime = optarg_2time(token);
            if (0 > endTime) {
                FREEUP(strTime);
                return -1;
            }
        }

        numTime ++;
    }
    FREEUP(strTime);

    if (0 == beginTime) {
        beginTime = time(NULL) - 3600*24;
    }

    if (0 == endTime) {
        endTime = time(NULL);
    }

    if (beginTime > endTime) {
        char btimebuf[TIMESTRINGLEN];
        char etimebuf[TIMESTRINGLEN];

        log_message(ERR, NOCLASS, "%s: the begin time (%s) is after the end time (%s).", __func__,
                    ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &beginTime, btimebuf),
                    ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &endTime, etimebuf));
        return -1;
    }

    times[0] = beginTime;
    times[1] = endTime;

    return 0;
} // end function set_request_times
